﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Master
{
    public abstract class TrafficMarshal
    {
        protected Dictionary<Car, DirectionData> registeredCars = new Dictionary<Car, DirectionData>();

        public void registerCar(DirectionData directionData, Car car)
        {
            if (!registeredCars.ContainsKey(car))
                registeredCars.Add(car, directionData);
        }

        public virtual void removeCar(Car car)
        {
            registeredCars.Remove(car);
        }

        public abstract Boolean canMoveForward(Car car);

        protected List<Car> getCarsWithOrientation(List<Car> cars, int orientation)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getOrientation() == orientation)
                    resultCars.Add(car);
            }
            return resultCars;
        }

        protected List<Car> getCarsWithDirection(List<Car> cars, Direction direction)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() == direction)
                    resultCars.Add(car);
            }
            return resultCars;
        }

        protected List<Car> getCarsWithDirectionData(List<Car> cars, DirectionData directionData)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() == directionData.getDirection() &&
                    carDirectionData.getOrientation() == directionData.getOrientation())
                    resultCars.Add(car);
            }
            return resultCars;
        }

        protected Boolean onlyCarsWithSameDirection(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != directionData.getDirection())
                    return false;
            }
            return true;
        }

        protected Boolean onlyCarsWithSameOrientation(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getOrientation() != directionData.getOrientation())
                    return false;
            }
            return true;
        }

        protected Boolean onlyCarsWithSameDirectionData(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != directionData.getDirection() ||
                    carDirectionData.getOrientation() != directionData.getOrientation())
                    return false;
            }
            return true;
        }

        protected Boolean onlyCarsGoingLeft(List<Car> cars)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != Direction.LEFT)
                    return false;
            }
            return true;
        }
    }

    public class SimpleTrafficMarshal : TrafficMarshal
    {
        /*************
         * VARIABLES *
         *************/
        private List<Car> movingCars = new List<Car>();

        /********************
         * PUBLIC FUNCTIONS *
         ********************/
        public override void removeCar(Car car)
        {
            base.removeCar(car);
            movingCars.Remove(car);
        }

        public override Boolean canMoveForward(Car car)
        {
            if (!registeredCars.ContainsKey(car)) throw new InvalidOperationException(); //unregistered car
            if (movingCars.Contains(car)) return true;
            else return checkIfCarCanMove(car);
        }

        /*********************
         * PRIVATE FUNCTIONS *
         *********************/
        private Boolean checkIfCarCanMove(Car car)
        {
            if ((movingCars.Count == 0 && registeredCars[car].getDirection() != Direction.LEFT) || checkIfPathIsFree(car))
            {
                movingCars.Add(car);
                return true;
            }
            return false;
        }

        private Boolean checkIfPathIsFree(Car car)
        {
            DirectionData directionData = registeredCars[car];
            if (directionData.getDirection() == Direction.LEFT) //car wants to take a left turn
                return (registeredCars.Count == 1 || onlyCarsWithSameDirectionData(movingCars, directionData)
                     || (onlyCarsGoingLeft(registeredCars.Keys.ToList()) && movingCars.Count == 0) || //there are only registered cars going left and no moving cars
                     (onlyCarsWithSameOrientation(movingCars, directionData) && 
                        onlyCarsGoingLeft(getCarsWithOrientation(registeredCars.Keys.ToList(), Toolkit.alterOrientationBy(directionData.getOrientation(), 180)))));
            foreach (Car movingCar in movingCars)
            {
                DirectionData movingCarDirectionData = registeredCars[movingCar];
                if (directionData.getOrientation() == movingCarDirectionData.getOrientation()) continue; //moving car is going in same direction --> no collisions possible
                if (!(directionData.getOrientation() == Toolkit.alterOrientationBy(movingCarDirectionData.getOrientation(), 180) //check if this moving car is coming from the opposite direction
                    && movingCarDirectionData.getDirection() != Direction.LEFT)) //check if the moving car does not want to make a left turn either
                    return false;
            }
            return true;
        }
    }
}